package com.example.airhockeyortho

import android.R.attr
import android.content.Context
import android.opengl.GLES30.GL_COLOR_BUFFER_BIT
import android.opengl.GLES30.GL_COMPILE_STATUS
import android.opengl.GLES30.GL_FLOAT
import android.opengl.GLES30.GL_FRAGMENT_SHADER
import android.opengl.GLES30.GL_LINES
import android.opengl.GLES30.GL_LINK_STATUS
import android.opengl.GLES30.GL_POINTS
import android.opengl.GLES30.GL_TRIANGLE_FAN
import android.opengl.GLES30.GL_VERTEX_SHADER
import android.opengl.GLES30.glAttachShader
import android.opengl.GLES30.glClear
import android.opengl.GLES30.glClearColor
import android.opengl.GLES30.glCompileShader
import android.opengl.GLES30.glCreateProgram
import android.opengl.GLES30.glCreateShader
import android.opengl.GLES30.glDeleteProgram
import android.opengl.GLES30.glDeleteShader
import android.opengl.GLES30.glDrawArrays
import android.opengl.GLES30.glEnableVertexAttribArray
import android.opengl.GLES30.glGetProgramiv
import android.opengl.GLES30.glGetShaderiv
import android.opengl.GLES30.glGetUniformLocation
import android.opengl.GLES30.glLinkProgram
import android.opengl.GLES30.glShaderSource
import android.opengl.GLES30.glUniformMatrix4fv
import android.opengl.GLES30.glUseProgram
import android.opengl.GLES30.glVertexAttribPointer
import android.opengl.GLES30.glViewport
import android.opengl.GLSurfaceView
import android.opengl.Matrix.orthoM
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10


class AirHockeyOrthoView(context : Context) : GLSurfaceView(context) {

    init {
        setEGLContextClientVersion(3)
        setRenderer(AirHockeyRenderer())
    }

    private inner class AirHockeyRenderer : Renderer {
        private var program = 0;
        private var uMatrixLocation = 0;

        private val projectionMatrix = FloatArray(16)

        private val modelMatrix = FloatArray(16)

        private lateinit var vertexData: FloatBuffer;

        private var tableVerticesWithTriangles: FloatArray =
            floatArrayOf(
                // Order of coordinates: X, Y, R, G, B
                // Triangle Fan
                0f, 0f, 1f, 1f, 1f,
                -0.5f, -0.8f, 0.7f, 0.7f, 0.7f,
                0.5f, -0.8f, 0.7f, 0.7f, 0.7f,
                0.5f, 0.8f, 0.7f, 0.7f, 0.7f,
                -0.5f, 0.8f, 0.7f, 0.7f, 0.7f,
                -0.5f, -0.8f, 0.7f, 0.7f, 0.7f,

                // Line 1
                -0.5f, 0f, 1f, 0f, 0f,
                0.5f, 0f, 1f, 0f, 0f,

                // Mallets
                0f, -0.4f, 0f, 0f, 1f,
                0f, 0.4f, 1f, 0f, 0f
            )

        override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f)

            program = createProgram(loadShader(GL_VERTEX_SHADER, "simple_vertex_shader.glsl"),
                loadShader(GL_FRAGMENT_SHADER, "simple_fragment_shader.glsl"))

            // Initialize the vertex buffer
            vertexData = ByteBuffer.allocateDirect(tableVerticesWithTriangles.size * Float.SIZE_BYTES)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
            vertexData.put(tableVerticesWithTriangles)

            glUseProgram(program);

            uMatrixLocation = glGetUniformLocation(program, "u_Matrix");

            vertexData.position(0);
            glVertexAttribPointer(0,
                2, GL_FLOAT, false, 5 * Float.SIZE_BYTES, vertexData);

            glEnableVertexAttribArray(0);

            // Bind our data, specified by the variable vertexData, to the vertex
            // attribute at location A_COLOR_LOCATION.
            vertexData.position(2);
            glVertexAttribPointer(1,
                3, GL_FLOAT, false, 5 * Float.SIZE_BYTES, vertexData);

            glEnableVertexAttribArray(1);

        }

        override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
            glViewport(0, 0, width, height)

            val aspectRatio =
                if (width > height) width.toFloat() / height.toFloat() else height.toFloat() / width.toFloat()

            if (width > height) {
                // Landscape
                orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f)
            } else {
                // Portrait or square
                orthoM(projectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f)
            }
        }

        override fun onDrawFrame(gl: GL10?) {
            // Clear the rendering surface.
            glClear(GL_COLOR_BUFFER_BIT);

            // Assign the matrix
            glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);

            // Draw the table.
            glDrawArrays(GL_TRIANGLE_FAN, 0, 6);

            // Draw the center dividing line.
            glDrawArrays(GL_LINES, 6, 2);

            // Draw the first mallet.
            glDrawArrays(GL_POINTS, 8, 1);

            // Draw the second mallet.
            glDrawArrays(GL_POINTS, 9, 1);
        }

        private fun loadShader(type: Int, filename: String): Int {
            val shaderSource = readShaderFileFromAssets(context, filename)
            val shader = glCreateShader(type)
            glShaderSource(shader, shaderSource)
            glCompileShader(shader)
            val compiled = IntArray(1)
            glGetShaderiv(shader, GL_COMPILE_STATUS, compiled, 0)
            if (compiled[0] == 0) {
                glDeleteShader(shader)
                throw RuntimeException("Could not compile shader: $filename")
            }
            return shader
        }

        private fun createProgram(vertexShader: Int, fragmentShader: Int): Int {
            val program = glCreateProgram()
            glAttachShader(program, vertexShader)
            glAttachShader(program, fragmentShader)
            glLinkProgram(program)
            val linkStatus = IntArray(1)
            glGetProgramiv(program, GL_LINK_STATUS, linkStatus, 0)
            if (linkStatus[0] == 0) {
                glDeleteProgram(program)
                throw RuntimeException("Could not link program")
            }
            return program
        }

        private fun readShaderFileFromAssets(context: Context, filename: String): String {
            val reader = BufferedReader(InputStreamReader(context.assets.open(filename)))
            val builder = StringBuilder()
            var line: String?
            while (reader.readLine().also { line = it } != null) {
                builder.append(line).append("\n")
            }
            reader.close()
            return builder.toString()
        }

    }
}